﻿using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using HandyControl.Data;
using HandyControl.Interactivity;
using HandyControl.Tools;
using HandyControl.Tools.Extension;

namespace HandyControl.Controls;

[TemplatePart(Name = ElementVisualBrush, Type = typeof(VisualBrush))]
public class Magnifier : AdornerElement
{
    private AdornerContainer _adornerContainer;

    private Size _viewboxSize;

    private const string ElementVisualBrush = "PART_VisualBrush";

    private VisualBrush _visualBrush = new();

    private readonly TranslateTransform _translateTransform;

    public static readonly DependencyProperty HorizontalOffsetProperty = DependencyProperty.Register(
        nameof(HorizontalOffset), typeof(double), typeof(Magnifier), new PropertyMetadata(ValueBoxes.Double0Box));

    public double HorizontalOffset
    {
        get => (double) GetValue(HorizontalOffsetProperty);
        set => SetValue(HorizontalOffsetProperty, value);
    }

    public static readonly DependencyProperty VerticalOffsetProperty = DependencyProperty.Register(
        nameof(VerticalOffset), typeof(double), typeof(Magnifier), new PropertyMetadata(ValueBoxes.Double0Box));

    public double VerticalOffset
    {
        get => (double) GetValue(VerticalOffsetProperty);
        set => SetValue(VerticalOffsetProperty, value);
    }

    public static Magnifier Default => new();

    public static readonly DependencyProperty ScaleProperty = DependencyProperty.Register(
        nameof(Scale), typeof(double), typeof(Magnifier), new PropertyMetadata(5.0, OnScaleChanged), ValidateHelper.IsInRangeOfPosDouble);

    private static void OnScaleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) => ((Magnifier) d).UpdateViewboxSize();

    public double Scale
    {
        get => (double) GetValue(ScaleProperty);
        set => SetValue(ScaleProperty, value);
    }

    public Magnifier()
    {
        _translateTransform = new TranslateTransform();
        RenderTransform = _translateTransform;
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        var visualBrush = GetTemplateChild(ElementVisualBrush) as VisualBrush ?? new VisualBrush();
        visualBrush.Viewbox = _visualBrush.Viewbox;
        _visualBrush = visualBrush;
    }

    protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
    {
        base.OnRenderSizeChanged(sizeInfo);
        UpdateViewboxSize();
    }

    private void UpdateViewboxSize() => _viewboxSize = new Size(ActualWidth / Scale, ActualHeight / Scale);

    protected sealed override void OnTargetChanged(FrameworkElement element, bool isNew)
    {
        base.OnTargetChanged(element, isNew);

        if (element == null) return;

        if (!isNew)
        {
            element.MouseEnter -= Element_MouseEnter;
            element.MouseLeave -= Element_MouseLeave;
            element.MouseMove -= Element_MouseMove;
            ElementTarget = null;
        }
        else
        {
            element.MouseEnter += Element_MouseEnter;
            element.MouseLeave += Element_MouseLeave;
            element.MouseMove += Element_MouseMove;
            ElementTarget = element;
        }
    }

    protected override void Dispose() => HideAdornerElement();

    private void UpdateLocation()
    {
        var targetPoint = Mouse.GetPosition(Target);
        var subX = targetPoint.X - _visualBrush.Viewbox.Width / 2;
        var subY = targetPoint.Y - _visualBrush.Viewbox.Height / 2;

        var targetVector = VisualTreeHelper.GetOffset(Target);
        _visualBrush.Viewbox = new Rect(new Point(subX + targetVector.X, subY + targetVector.Y), _viewboxSize);

        var adornerPoint = Mouse.GetPosition(_adornerContainer);
        _translateTransform.X = adornerPoint.X + HorizontalOffset;
        _translateTransform.Y = adornerPoint.Y + VerticalOffset;
    }

    private void Element_MouseMove(object sender, MouseEventArgs e) => UpdateLocation();

    private void Element_MouseLeave(object sender, MouseEventArgs e) => HideAdornerElement();

    private void Element_MouseEnter(object sender, MouseEventArgs e) => ShowAdornerElement();

    private void HideAdornerElement()
    {
        if (_adornerContainer == null) return;
        var layer = AdornerLayer.GetAdornerLayer(Target);

        if (layer != null)
        {
            layer.Remove(_adornerContainer);
        }
        else if (_adornerContainer is { Parent: AdornerLayer parent })
        {
            parent.Remove(_adornerContainer);
        }

        if (_adornerContainer != null)
        {
            _adornerContainer.Child = null;
            _adornerContainer = null;
        }
    }

    private void ShowAdornerElement()
    {
        if (_adornerContainer == null)
        {
            var layer = AdornerLayer.GetAdornerLayer(Target);
            if (layer == null) return;

            _adornerContainer = new AdornerContainer(layer)
            {
                Child = this
            };
            layer.Add(_adornerContainer);
        }
        this.Show();
    }
}
